前端测试服 Docker + Nginx 发布优化:从 docker build 到 SFTP 直接发布
纯 Vue + Nginx 静态前端,每次发测试服都 docker build 没有必要。把 www 从镜像里拿出来,挂到宿主机目录上,之后只 SFTP 上传即可。
一、背景
旧流程:
本地构建 → SFTP 上传 → SSH 登录 → 执行 docker.sh
→ 停容器 → 删镜像 → docker build → 再启动docker.sh 大致是:
docker stop <容器名>
docker rm <容器名>
docker rmi <镜像名>
docker build -t <镜像名> .
docker run ...静态文件变了,镜像层却要整份重做。目标流程:
本地 npm run build → 生成 www/ → VS Code SFTP 上传 www/ → Nginx 直接读 → 完成日常发版只上传文件,不再执行 Docker 命令。
二、原来的 Dockerfile
FROM nginx:stable-alpine
COPY ./www/ /var/www/
COPY nginx.conf /etc/nginx/conf.d/conf.template
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV NGINX_HOST=localhost \
NGINX_PORT=80
CMD envsubst '$NGINX_HOST $NGINX_PORT' < /etc/nginx/conf.d/conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'根因是这一行:
COPY ./www/ /var/www/文件被打进镜像:
本地 www/ → docker build → 镜像 → 容器 /var/wwwwww 一变就必须重新 docker build。Volume 挂上之后,运行时会盖住镜像里这份目录,COPY ./www/ 实际不再被用到;想瘦镜像可以以后再删掉它,但那属于改 Dockerfile,要走下面第十四节的重建流程。
三、原来的 Nginx 配置
location / {
root /var/www;
try_files $uri /index.html;
index index.html;
gzip on;
gzip_types text/css text/javascript application/x-javascript application/json;
}Nginx 读的就是容器内 /var/www。改造只做一件事:让这个路径对应宿主机上的 www。
四、Docker Volume 挂载
服务器目录:
/home/bjobd/obd-macro-monitor-web/main-web
└── www # 前端静态资源映射:
宿主机 /home/bjobd/obd-macro-monitor-web/main-web/www
↓ bind mount
容器 /var/www启动(首次,或容器被删后):
docker run -d \
-v /home/bjobd/obd-macro-monitor-web/main-web/www:/var/www \
-v /home/bjobd/obd-macro-monitor-web/main-web/log:/var/log/nginx \
-p 9077:80 \
--name obd-macro-monitor-main-web \
--restart unless-stopped \
bjobd/obd-macro-monitor-main-web关键参数是 -v 宿主机www:/var/www。这里用的是 bind mount(绑宿主机路径),不是匿名 Volume。
五、改造前后对比
| 旧方案 | Volume 挂载 | |
|---|---|---|
| 文件怎么进容器 | www → docker build → 镜像 → 容器 | 宿主机 www → 挂载 → 容器 /var/www |
| 日常发版 | 重建镜像、重建容器 | 只改宿主机文件 |
| 要不要重启 Nginx | 要 | 一般不要 |
六、VS Code SFTP 配置
.vscode/sftp.json:
{
"name": "测试服",
"host": "192.200.125.79",
"protocol": "sftp",
"port": 22,
"username": "deploy",
"privateKeyPath": "~/.ssh/id_ed25519",
"remotePath": "/home/bjobd/obd-macro-monitor-web/main-web/www",
"context": "./www",
"uploadOnSave": false,
"useTempFile": true,
"openSsh": false
}几点:
- 用密钥,不要把密码写进仓库。
context是本地构建产物目录,remotePath是服务器上被挂载的www。上传的是www里面的index.html、assets/,不要再套一层www/www/。useTempFile: true:先传临时文件再改名,降低上传过程中 Nginx 读到半截文件的概率。
本地步骤:
npm run build然后用插件把 www/ 同步到 remotePath。
七、为什么容器跑着也能上传
改的不是容器可写层,改的是宿主机磁盘。容器里的 /var/www 只是同一块目录的挂载点:
VS Code SFTP
↓
宿主机 /home/bjobd/obd-macro-monitor-web/main-web/www
↓ bind mount
容器 /var/www
↓
Nginx 读文件对外服务因此日常发版 不必重启容器。两点例外:
- Nginx 若开了
open_file_cache,可能短期内仍吐旧 inode,这时docker exec <容器> nginx -s reload即可,仍然不用重建镜像。 - 浏览器会缓存
index.html/ 带 hash 的资源。Vue 构建产物文件名变了一般没问题;若页面仍是旧的,先强制刷新。
八、权限问题
用 deploy 上传时出现:
Error: Permission denied项目在 /home/bjobd/... 下,属主是 bjobd。SFTP 登录的是 deploy。Linux 进入目录需要沿途每一层都有 x(执行)权限,写入 www 还要有写权限。缺任何一层都会 Permission denied。
九、创建独立部署账号
sudo useradd -m -s /bin/bash deploy
sudo passwd deploy
id deploy不要把 deploy 加进 docker 组,除非这个账号还要在服务器上执行 docker 命令。日常 SFTP 上传不需要 Docker 权限;能操作 Docker 等于能拿到 root。
十、目录权限
ls 里 main-web 的 + 表示 ACL。家目录不能直接 chmod 777,用 ACL 只给 deploy 穿过路径、并让 www 归它所有:
# 穿过 bjobd 家目录(只要 x,不必给读)
sudo setfacl -m u:deploy:--x /home/bjobd
sudo setfacl -m u:deploy:rx /home/bjobd/obd-macro-monitor-web
sudo setfacl -m u:deploy:rx /home/bjobd/obd-macro-monitor-web/main-web
# www 交给 deploy,SFTP 才能覆盖构建产物
sudo chown -R deploy:deploy /home/bjobd/obd-macro-monitor-web/main-web/www
sudo chmod -R u+rwX,g+rX,o+rX /home/bjobd/obd-macro-monitor-web/main-web/www
# Nginx 进程要能读这些文件。alpine 镜像里 nginx 通常以 nginx 用户运行,
# 上面 o+rX 已覆盖;若容器内用户读不到,再补:
# sudo chmod -R o+rX /home/bjobd/obd-macro-monitor-web/main-web/www预期:
drwxrwxr-x+ 4 bjobd bjobd ... main-web
drwxr-xr-x 3 deploy deploy ... www
-rw-r--r-- 1 deploy deploy ... index.htmlgetfacl /home/bjobd 应能看到 user:deploy:--x。
容器内 Nginx 读的是挂载进来的同一批文件。属主是 deploy 没关系,只要 others 或 ACL 允许读取。
十一、校验挂载是否生效
docker inspect obd-macro-monitor-main-web \
--format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'预期:
/home/bjobd/obd-macro-monitor-web/main-web/www -> /var/www
/home/bjobd/obd-macro-monitor-web/main-web/log -> /var/log/nginx十二、验证文件实时同步
ls -li /home/bjobd/obd-macro-monitor-web/main-web/www/index.html
docker exec obd-macro-monitor-main-web ls -li /var/www/index.html
curl -s http://127.0.0.1:9077/index.html | head两边 inode 应相同。SFTP 上传新文件后再 curl,响应体应是新内容。
十三、日常发布
本地开发 → npm run build → VS Code SFTP 同步 www → 完成Nginx 容器一直跑着。不再 SSH、不再 docker build、不再删镜像。
十四、什么时候还要动 Docker
普通前端发版:不需要。
| 场景 | 做什么 |
|---|---|
| 首次部署 / 容器被删 | docker run(带上 -v) |
改 nginx.conf | 配置在镜像里的话要重建镜像并换容器;若同样改成挂载,只需把文件放到挂载路径后 nginx -s reload |
| 改 Dockerfile / 基础镜像 / 时区脚本 | docker build 后再换容器 |
十五、最终架构
本地开发
│ npm run build
↓
www/
│ VS Code SFTP
↓
┌──────────────────────────────────────────┐
│ 测试服务器 │
│ /home/bjobd/obd-macro-monitor-web/ │
│ main-web/www │
│ │ bind mount │
│ ↓ │
│ 容器 /var/www │
│ ↓ │
│ Nginx :9077 │
└────────────────────┼─────────────────────┘
↓
浏览器测试服发版两步:npm run build,再 SFTP 上传 www。
